#include "env.h"

#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>

#include <anet/util.h>
#include <anet/anet.h>

#include "array.h"
#include "hashtable.h"
#include "slice.h"
#include "client.h"
#include "game.h"
#include "role.h"

static void RandomName(char* name) {
    struct timeval tv; 
    gettimeofday(&tv, NULL);
    unsigned long long t = tv.tv_sec * 1000 + tv.tv_usec / 1000;
    snprintf(name, 64, "%llu", t);
}

static struct Game* AutoJoinIn(struct Env* env, struct Client* client) {
    if (client->data != NULL) return NULL;

    HashTableIterator itr = HashTableGetIterator(env->games);
    Game* game = NULL;
    void* data = NULL;
    while ((data = HashTableNext(&itr)) != NULL) {
        game = (Game *)data;
        if (game->status != GAME_IDLE) continue;
        if (GameJoinIn(game, client) >= 0) return game;
    }

    char name[64];
    RandomName(name);
    game = GameCreate(name);
    GameJoinIn(game, client);
    Slice n = SliceCreate2(name, strlen(name));
    HashTableInsert(env->games, n, game);
    return game;
}

static struct Game* Leave(struct Env* env, struct Client* client) {
    if (client->data == NULL) return NULL;
    Role* role = (Role *)client->data;
    Slice name = SliceCreate2(role->group, strlen(role->group));
    Game* game = (Game *)HashTableFind(env->games, name);
    if (game) {
        switch (role->who) {
            case ROLE_HERO: game->hero = NULL; break;
            case ROLE_SWORD: game->sword = NULL; break;
            case ROLE_BOSS: game->boss = NULL; break; 
        }
        if (game->status == GAME_RUNNING) {
          game->status = GAME_OVER;
        }
        if (game->hero == NULL && game->sword == NULL && game->boss == NULL) {
            HashTableRemove(env->games, name);
            GameDestroy(game);
            game = NULL;
        }
    }
    RoleDestroy(role);
    client->data = NULL;

    SliceDestroy(name);

    return game;
}

static struct Game* DoAction(struct Env* env, struct Client* client, int action) {
    if (client->data == NULL) return NULL;
    Role* role = (Role *)client->data;
    switch (role->who) {
        case ROLE_HERO:
            {
                Hero* hero = (Hero *)role->target;
                hero->status = action;
                LogInfo("hero action %d", hero->status);
            }
            break;
        case ROLE_SWORD:
            {
                Sword* sword = (Sword *)role->target;
                sword->status = action;
                LogInfo("sword action %d", sword->status);
            }
            break;
        case ROLE_BOSS:
            {
                Boss* boss = (Boss *)role->target;
                boss->status = action;
                LogInfo("boss action %d", boss->status);
            }
            break;
    }

    Slice name = SliceCreate2(role->group, strlen(role->group));
    Game* game = (Game *)HashTableFind(env->games, name);
    if (GameIsPreparedToJudge(game)) {
        LogInfo("game s prepared to judge: %s", game->name);
        GameProceed(game);
    }
    SliceDestroy(name);
    return game;
}

static void AddClient(Env* env, Client* client) {
    if (env->clients == NULL) {
        env->clients = ArrayCreate(2);
    }
    ArrayPushBack(env->clients, client);
}

static void RemoveClient(Env* env, Client* client) {
    if (env->clients) {
        void* t = ArrayRemove(env->clients, client);
        ClientDestroy((Client *)t);
    }
}

struct Env* EnvCreate() {
    struct Env* env = (struct Env*)malloc(sizeof(struct Env));

    env->listenfd      = -1;
    env->loglevel      = kError;
    env->host[0]       = '\0';
    env->port          = 0;
    env->run_daemonize = 0;
    env->logfile       = strdup("stderr");

    env->clients = NULL;
    env->AddClient = AddClient;
    env->RemoveClient = RemoveClient;

    env->games = StringKeyHashTableCreate(1, GameDestroy);
    env->AutoJoinIn = AutoJoinIn;
    env->Leave  = Leave;
    env->DoAction = DoAction;

    srand(time(NULL));

    return env;
}

static void Usage() {
    printf("usage:\n"
            "  gameserver [options]\n"
            "options:\n"
            "  -e host:port specify local listen, default 127.0.0.1:12345\n"
            "  -l file    specify log file\n"
            "  -d         run in background\n"
            "  -v         show detailed log\n"
            "  --version  show version and exit\n"
            "  -h         show help and exit\n\n"
            "examples:\n"
            "  gameserver \"-e host:port\"\n\n"
          );
    exit(EXIT_SUCCESS);
}


Env* EnvParseArgs(int argc, char** argv) {
    Env* env = EnvCreate();
    if (env == NULL) return NULL;

    int i, j;

    InitLogger((LogLevel)env->loglevel, NULL);

    for (i = 1; i < argc; i++) {
        if (argv[i][0] == '-') {
            if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
                Usage();
            } else if (!strcmp(argv[i], "--version")) {
                printf("%s %s\n\n", PROGRAM, VERSION);
                exit(EXIT_SUCCESS);
            } else if (!strcmp(argv[i], "-d")) {
                env->run_daemonize = 1;
            } else if (!strcmp(argv[i], "-l")) {
                if (++i >= argc) LogFatal("file name must be specified");
                env->logfile = strdup(argv[i]);
            } else if (!strncmp(argv[i], "-v", 2)) {
                for (j = 1; argv[i][j] != '\0'; j++) {
                    if (argv[i][j] == 'v') env->loglevel++;
                    else LogFatal("invalid argument %s", argv[i]);
                }
            } else if (!strcmp(argv[i], "-e")) {
                if (++i >= argc) LogFatal("port must be specified");
                if (ParseEndPoint(argv[i], env->host, &env->port) != 0) {
                    LogFatal("invalid endpoint: %s", argv[i]);
                }
            } else {
                LogFatal("unknow option %s\n", argv[i]);
            }
        }
    }

    if (env->port == 0) {
        strcpy(env->host, "127.0.0.1");
        env->port = 12345;
    }

    InitLogger((LogLevel)env->loglevel, env->logfile);

    return env;
}


void EnvDestroy(struct Env* env) {
    if (env) {
        if (env->listenfd >= 0) {
            anetClose(env->listenfd);
        }
        if (env->clients) {
            ArrayIterator itr = ArrayGetIterator(env->clients);
            void** t = NULL;
            while ((t = itr.next(&itr)) != NULL) {
                ClientDestroy((Client *)(*t));
            }
            ArrayDestroy(env->clients);
        }
        if (env->logfile) free(env->logfile);
        if (env->games) HashTableDestroy(env->games);
        free(env);
    }
}

